// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>

#include <acpica/acpi.h>

static inline void do_indent(unsigned int level) {
  while (level) {
    printf("  ");
    level--;
  }
}

#define INDENT_PRINTF(...) \
  do {                     \
    do_indent(level);      \
    printf(__VA_ARGS__);   \
  } while (0)

enum print_resource_request {
  CURRENT_RESOURCES,
  POSSIBLE_RESOURCES,
};

static ACPI_STATUS acpi_print_resources(ACPI_HANDLE object, unsigned int level,
                                        enum print_resource_request type) {
  ACPI_BUFFER buffer = {
      .Length = ACPI_ALLOCATE_BUFFER,
      .Pointer = NULL,
  };
  ACPI_STATUS status = AE_BAD_PARAMETER;
  if (type == POSSIBLE_RESOURCES) {
    status = AcpiGetPossibleResources(object, &buffer);
  } else if (type == CURRENT_RESOURCES) {
    status = AcpiGetCurrentResources(object, &buffer);
  } else {
    printf("Invalid resource type to print\n");
    return AE_BAD_PARAMETER;
  }

  if (status != AE_OK) {
    if (buffer.Pointer) {
      AcpiOsFree(buffer.Pointer);
    }
    return status;
  }
  if (type == POSSIBLE_RESOURCES) {
    INDENT_PRINTF("PRS:\n");
  } else if (type == CURRENT_RESOURCES) {
    INDENT_PRINTF("CRS:\n");
  }

  uintptr_t entry_addr = (uintptr_t)buffer.Pointer;
  ACPI_RESOURCE* res = (ACPI_RESOURCE*)entry_addr;
  level += 1;
  while (res->Type != ACPI_RESOURCE_TYPE_END_TAG) {
    INDENT_PRINTF("Entry: ");
    level += 1;
    switch (res->Type) {
      case ACPI_RESOURCE_TYPE_IO: {
        printf("IO\n");
        ACPI_RESOURCE_IO* io = &res->Data.Io;
        INDENT_PRINTF("io_decode: %d\n", io->IoDecode);
        INDENT_PRINTF("alignment: %d\n", io->Alignment);
        INDENT_PRINTF("addrlen: %d\n", io->AddressLength);
        INDENT_PRINTF("address min: %#04x\n", io->Minimum);
        INDENT_PRINTF("address max: %#04x\n", io->Maximum);
        break;
      }
      case ACPI_RESOURCE_TYPE_ADDRESS16: {
        printf("Address16\n");
        ACPI_RESOURCE_ADDRESS16* a16 = &res->Data.Address16;
        INDENT_PRINTF("res_type: %d\n", a16->ResourceType);
        INDENT_PRINTF("produce_consume: %d\n", a16->ProducerConsumer);
        INDENT_PRINTF("decode: %d\n", a16->Decode);
        INDENT_PRINTF("min_addr_fixed: %d\n", a16->MinAddressFixed);
        INDENT_PRINTF("max_addr_fixed: %d\n", a16->MaxAddressFixed);
        INDENT_PRINTF("address granularity: %#04x\n", a16->Address.Granularity);
        INDENT_PRINTF("address min: %#04x\n", a16->Address.Minimum);
        INDENT_PRINTF("address max: %#04x\n", a16->Address.Maximum);
        INDENT_PRINTF("address xlat offset: %#04x\n", a16->Address.TranslationOffset);
        INDENT_PRINTF("address len: %#04x\n", a16->Address.AddressLength);
        // TODO: extract MTRR info from a16->Info
        break;
      }
      case ACPI_RESOURCE_TYPE_ADDRESS32: {
        printf("Address32\n");
        ACPI_RESOURCE_ADDRESS32* a32 = &res->Data.Address32;
        INDENT_PRINTF("res_type: %d\n", a32->ResourceType);
        INDENT_PRINTF("produce_consume: %d\n", a32->ProducerConsumer);
        INDENT_PRINTF("decode: %d\n", a32->Decode);
        INDENT_PRINTF("min_addr_fixed: %d\n", a32->MinAddressFixed);
        INDENT_PRINTF("max_addr_fixed: %d\n", a32->MaxAddressFixed);
        INDENT_PRINTF("address granularity: %#08x\n", a32->Address.Granularity);
        INDENT_PRINTF("address min: %#08x\n", a32->Address.Minimum);
        INDENT_PRINTF("address max: %#08x\n", a32->Address.Maximum);
        INDENT_PRINTF("address xlat offset: %#08x\n", a32->Address.TranslationOffset);
        INDENT_PRINTF("address len: %#08x\n", a32->Address.AddressLength);
        // TODO: extract MTRR info from a32->Info
        break;
      }
      case ACPI_RESOURCE_TYPE_IRQ: {
        printf("IRQ\n");
        ACPI_RESOURCE_IRQ* irq = &res->Data.Irq;
        INDENT_PRINTF("trigger: %s\n", irq->Triggering == ACPI_EDGE_SENSITIVE ? "edge" : "level");
        const char* pol = "invalid";
        switch (irq->Polarity) {
          case ACPI_ACTIVE_BOTH:
            pol = "both";
            break;
          case ACPI_ACTIVE_LOW:
            pol = "low";
            break;
          case ACPI_ACTIVE_HIGH:
            pol = "high";
            break;
        }
        INDENT_PRINTF("polarity: %s\n", pol);
        INDENT_PRINTF("sharable: %d\n", irq->Sharable);
        INDENT_PRINTF("wake_cap: %d\n", irq->WakeCapable);
        for (unsigned int i = 0; i < irq->InterruptCount; ++i) {
          INDENT_PRINTF("irq #%d: %d\n", i, irq->Interrupts[i]);
        }
        break;
      }
      case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: {
        printf("Extended IRQ\n");
        ACPI_RESOURCE_EXTENDED_IRQ* irq = &res->Data.ExtendedIrq;
        INDENT_PRINTF("produce_consume: %d\n", irq->ProducerConsumer);
        INDENT_PRINTF("trigger: %s\n", irq->Triggering == ACPI_EDGE_SENSITIVE ? "edge" : "level");
        const char* pol = "invalid";
        switch (irq->Polarity) {
          case ACPI_ACTIVE_BOTH:
            pol = "both";
            break;
          case ACPI_ACTIVE_LOW:
            pol = "low";
            break;
          case ACPI_ACTIVE_HIGH:
            pol = "high";
            break;
        }
        INDENT_PRINTF("polarity: %s\n", pol);
        INDENT_PRINTF("sharable: %d\n", irq->Sharable);
        INDENT_PRINTF("wake_cap: %d\n", irq->WakeCapable);
        for (unsigned int i = 0; i < irq->InterruptCount; ++i) {
          INDENT_PRINTF("irq #%d: %d\n", i, irq->Interrupts[i]);
        }
        break;
      }
      default:
        printf("Unknown (type %u)\n", res->Type);
    }
    level -= 1;

    entry_addr += res->Length;
    res = (ACPI_RESOURCE*)entry_addr;
  }
  level -= 1;

  AcpiOsFree(buffer.Pointer);
  return AE_OK;
}

static ACPI_STATUS acpi_get_pcie_devices_crs(ACPI_HANDLE object, UINT32 nesting_level,
                                             void* context, void** ret) {
  printf("Found object %p\n", object);
  return acpi_print_resources(object, 1, CURRENT_RESOURCES);
}

static void acpi_debug_pcie_crs(void) {
  ACPI_STATUS status = AcpiGetDevices((char*)"PNP0A08", acpi_get_pcie_devices_crs, NULL, NULL);
  if (status != AE_OK) {
    printf("Could not find PCIe root complex\n");
  }
}

static ACPI_STATUS acpi_print_prt(unsigned int level, ACPI_HANDLE object) {
  ACPI_STATUS status = AE_OK;

  ACPI_BUFFER buffer = {
      // Request that the ACPI subsystem allocate the buffer
      .Length = ACPI_ALLOCATE_BUFFER,
      .Pointer = NULL,
  };
  status = AcpiGetIrqRoutingTable(object, &buffer);
  if (status != AE_OK) {
    if (buffer.Pointer) {
      AcpiOsFree(buffer.Pointer);
    }
    return status;
  }
  assert(buffer.Pointer);

  uintptr_t entry_addr = (uintptr_t)buffer.Pointer;
  ACPI_PCI_ROUTING_TABLE* entry;
  for (entry = (ACPI_PCI_ROUTING_TABLE*)entry_addr; entry->Length != 0;
       entry_addr += entry->Length, entry = (ACPI_PCI_ROUTING_TABLE*)entry_addr) {
    assert(entry_addr <= (uintptr_t)buffer.Pointer + buffer.Length);

    INDENT_PRINTF("Entry:\n");
    level += 1;
    if (entry->Pin > 3) {
      INDENT_PRINTF("Pin: Invalid (%08x)\n", entry->Pin);
    } else {
      INDENT_PRINTF("Pin: INT%c\n", 'A' + entry->Pin);
    }
    INDENT_PRINTF("Address: %#016llx\n", entry->Address);
    level += 1;
    INDENT_PRINTF("Dev ID: %#04x\n", (uint16_t)(entry->Address >> 16));
    level -= 1;

    if (entry->Source[0]) {
      // If the Source is not just a NULL byte, then it refers to a
      // PCI Interrupt Link Device
      INDENT_PRINTF("Source: %s\n", entry->Source);
      INDENT_PRINTF("Source Index: %u\n", entry->SourceIndex);
      ACPI_HANDLE ild;
      status = AcpiGetHandle(object, entry->Source, &ild);
      if (status != AE_OK) {
        INDENT_PRINTF("Could not lookup Interrupt Link Device\n");
        continue;
      }
      status = acpi_print_resources(ild, 2, CURRENT_RESOURCES);
      if (status != AE_OK) {
        INDENT_PRINTF("Could not lookup ILD CRS\n");
      }
      status = acpi_print_resources(ild, 2, POSSIBLE_RESOURCES);
      if (status != AE_OK) {
        INDENT_PRINTF("Could not lookup ILD PRS\n");
      }
    } else {
      // Otherwise, it just refers to a global IRQ number that the pin
      // is connected to
      INDENT_PRINTF("GlobalIRQ: %u\n", entry->SourceIndex);
    }
    level -= 1;
  }

  AcpiOsFree(buffer.Pointer);
  return AE_OK;
}

static ACPI_STATUS acpi_get_pcie_devices_irq(ACPI_HANDLE object, UINT32 nesting_level,
                                             void* context, void** ret) {
  ACPI_STATUS status = acpi_print_prt(nesting_level, object);
  if (status != AE_OK) {
    printf("Failed to print PRT for root complex\n");
    return status;
  }

  // Enumerate root ports
  ACPI_HANDLE child = NULL;
  while (1) {
    status = AcpiGetNextObject(ACPI_TYPE_DEVICE, object, child, &child);
    if (status == AE_NOT_FOUND) {
      break;
    } else if (status != AE_OK) {
      printf("Failed to get next child object of root complex\n");
      return status;
    }

    ACPI_OBJECT object = {0};
    ACPI_BUFFER buffer = {
        .Length = sizeof(object),
        .Pointer = &object,
    };
    status = AcpiEvaluateObject(child, (char*)"_ADR", NULL, &buffer);
    if (status != AE_OK || buffer.Length < sizeof(object) || object.Type != ACPI_TYPE_INTEGER) {
      continue;
    }
    UINT64 data = object.Integer.Value;
    unsigned int level = nesting_level;
    INDENT_PRINTF("Device %#02x Function %#01x:\n", (uint8_t)(data >> 16), (uint8_t)(data & 0x7));
    status = acpi_print_prt(nesting_level + 1, child);
    if (status != AE_OK) {
      continue;
    }
  }

  return AE_OK;
}

static void acpi_debug_pcie_irq_routing(void) {
  ACPI_STATUS status = AcpiGetDevices((char*)"PNP0A08", acpi_get_pcie_devices_irq, NULL, NULL);
  if (status != AE_OK) {
    printf("Could not enumerate PRTs\n");
  }
}

static ACPI_STATUS acpi_debug_print_device_name(ACPI_HANDLE object, UINT32 nesting_level,
                                                void* context, void** ret) {
  ACPI_DEVICE_INFO* info = NULL;
  ACPI_STATUS status = AcpiGetObjectInfo(object, &info);
  if (status != AE_OK) {
    if (info) {
      ACPI_FREE(info);
    }
    return status;
  }

  unsigned int level = nesting_level;
  INDENT_PRINTF("%4s\n", (char*)&info->Name);

  ACPI_FREE(info);
  return AE_OK;
}

static void acpi_debug_walk_ns(void) {
  ACPI_STATUS status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, INT_MAX,
                                         acpi_debug_print_device_name, NULL, NULL, NULL);
  if (status != AE_OK) {
    printf("Failed to walk namespace\n");
  }
}
